package com.nirvana.generate;

import com.nirvana.config.PluginConfig;
import com.nirvana.data.DependenceData;
import com.nirvana.factory.ItemFactory;
import com.nirvana.item.CommonItem;
import com.nirvana.item.DefPropertyItem;
import com.nirvana.type.ItemType;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;

import static com.nirvana.config.PluginConfig.PLUGIN_GENERATE_FLAG;
import static com.nirvana.config.PluginConfig.collectionPattern;

public class DefXmlWrapperClassGenerator implements GenerateInterface {
    private static DefXmlWrapperClassGenerator INSTANCE;
    private final CommonItem commonItem = ItemFactory.getCommonItem(ItemType.DefPropertyItem);

    private DefXmlWrapperClassGenerator() {

    }

    public static DefXmlWrapperClassGenerator getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new DefXmlWrapperClassGenerator();
        }
        return INSTANCE;
    }

    @Override
    public void generate() {
        StringBuilder content = new StringBuilder();
        StringBuilder packageContent = new StringBuilder();
        StringBuilder importContent = new StringBuilder();
        StringBuilder classContent = new StringBuilder();
        StringBuilder fieldContent = new StringBuilder();

        Set<String> hasImport = new HashSet<>();

        generatePackage(packageContent);
        commonItem.itemList.forEach(item -> {
            if (item instanceof DefPropertyItem defPropertyItem) {
                generateImport(importContent, defPropertyItem, hasImport);
                generateField(fieldContent, defPropertyItem);
            }
        });

        if (PluginConfig.getInstance().extendsGroupDef()) {
            importContent.append("""
                    import com.nirvana.net.GameServerHolder
                    """);
            fieldContent.append("""
                        def openGroup: Array[Int] = raw.open_groups
                        def offGroup: Array[Int] = raw.off_groups
                    """);
        }

        generateClass(classContent, fieldContent);

        String defName = commonItem.textField.getText();
        content
                .append(packageContent)
                .append("""
                        import com.nirvana.bean.xmlBean.%s
                        """.formatted(defName))
                .append(importContent)
                .append(classContent);

        String flatBuffersBasePath = PluginConfig.getInstance().flatBuffersBasePath();
        Path basePath = Paths.get(flatBuffersBasePath);
        try {
            if (Files.notExists(basePath)) {
                Files.createDirectories(basePath);
            }
            Path path = Paths.get(flatBuffersBasePath + "/" + defName + "XmlWrapper.scala");
            Files.writeString(path, content.toString());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void generatePackage(StringBuilder packageContent) {
        packageContent.append("""
                package %s
                
                """.formatted(PluginConfig.getInstance().flatBuffersDotPackage()));
    }

    private void generateImport(StringBuilder importContent, DefPropertyItem item, Set<String> hasImport) {
        if (!item.isResolve()) return;

        String propertyType = item.getPropertyType();
        String[] types = propertyType.replaceAll("[^a-zA-Z0-9]", " ").split("\\s+");
        for (String type : types) {
            if (hasImport.contains(type) || StringUtils.isBlank(type)) continue;

            DependenceData dependenceDataByType = PluginConfig.getInstance().getDependenceDataByType(type);

            if (dependenceDataByType == null) continue;

            // 导入依赖类
            if (dependenceDataByType.canImport()) {
                importContent.append("""
                import %s
                """.formatted(dependenceDataByType.getFullClassName()));
            }
            hasImport.add(type);
        }

    }

    private void generateClass(StringBuilder classContent, StringBuilder fieldContent) {
        String defName = commonItem.textField.getText();
        classContent.append("""
                
                final class %sXmlWrapper(private val raw: %s) extends %sBase {
                %s
                }
                %s
                """.formatted(
                defName,
                defName,
                defName,
                fieldContent.toString(),
                PLUGIN_GENERATE_FLAG
        ));
    }

    private void generateField(StringBuilder fieldContent, DefPropertyItem item) {
        String propertyType = item.getPropertyType();
        String propertyName = item.getPropertyName();
        fieldContent.append("""
                    /** %s */
                    def %s: %s = raw.%s
                """.formatted(item.getComment(), propertyName, propertyType, propertyName));
        Matcher matcher = collectionPattern.matcher(propertyType);
        if (matcher.find()) {
            String subType = matcher.group(2);
            String subTypeParam = subType;
            if (subType.contains("(")) subTypeParam = "(" + subType + ")";
            fieldContent.append("""
                    def %s_foreach(action: %s => Unit): Unit = raw.%s.foreach(action)
                    def %s_filter(action: %s => Boolean): Seq[%s] = raw.%s.filter(action)
                    def %s_exists(action: %s => Boolean): Boolean = raw.%s.exists(action)
                    def %s_find(action: %s => Boolean): Option[%s] = raw.%s.find(action)
                    def %s_map[T](action: %s => T): Seq[T] = raw.%s.map(action)
                    def %s_take(n: Int): %s = raw.%s.take(n)
                    def %s_drop(n: Int): %s = raw.%s.drop(n)
                    def %s_slice(from: Int, until: Int): %s = raw.%s.slice(from, until)
                """.formatted(
                        // foreach
                        propertyName,
                        subTypeParam,
                        propertyName,

                        // filter
                        propertyName,
                        subTypeParam,
                        subType,
                        propertyName,

                        // exists
                        propertyName,
                        subTypeParam,
                        propertyName,

                        // find
                        propertyName,
                        subTypeParam,
                        subType,
                        propertyName,

                        // map
                        propertyName,
                        subTypeParam,
                        propertyName,

                        // take
                        propertyName,
                        propertyType,
                        propertyName,

                        // drop
                        propertyName,
                        propertyType,
                        propertyName,

                        // slice
                        propertyName,
                        propertyType,
                        propertyName
            ));
        }
    }

}
